/* 
Copyright (c) 2004,2005, 2006, 2007, 2008 University of Illinois 

All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

1. Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the distribution.

3. The name of the author may not be used to endorse or promote products
   derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

/* Functions for the Log of DHTML/Widgets 
 * romanows, 3/10/06
 */

// FIXME: DOMInserted and Removed events are not implemented for widgets; I have no test cases, yet.

/**
 * How's this going to work?
 * onLoad this script is called.  Build dataarray of widgets.<b> 
 * On each widget, add a handler for all user input events.
 * (When this handler is called, it should check to see if the widget has a corresponding 
 * event handler and generate a new log object if so.  To do this, remove the event handler 
 * that's being called and check to see if the node.onclick (for example) property is not void, 
 * then add your event handler again.  Perhaps with timestamp information so the new event handler 
 * can make sure it's not called again for the exact same event.  Yikes.)
 * Register DOM Mutate event handlers on the DOM root and log all changes to widget nodes or states
 * When widgets inserted, be sure to insert input event handlers on them 
*/
AccessibilityExtension.ns(function() { with (AccessibilityExtension.LIB) {

	var AE = AccessibilityExtension.ns;  
	AE.dialogName = "accessext-form-list-dhtmllog";	

	var listbox = null;
	var logarray = new Array();
	var tbObj;
	
	var userEventsToAdd = [
		'click',
		'keypress',
		'focus',
		'blur',
	];
	
	var userEventsToRemove = [];
	
	function logObjectDisplayDetails() {}
	logObjectDisplayDetails.prototype = {
		dataElementNames : ['order','timestamp','widgetID','state','event','target','description'],
		columnHeaders : ['column.header.order.label','column.header.timestamp.label','column.header.widgetID.label','column.header.state.label', 'column.header.event.label', 'column.header.target.label', 'column.header.description.label'],
		
		accessIssuesDataElementNames : [],
		accessIssuesMessages : [],
	}
	var logObjectDisplayDetails = new logObjectDisplayDetails();
	
	function logObject(node,order,timestamp,widgetID,state,event,target,description) {
		this.node = node;
		this.order = order;
		this.timestamp = timestamp;
		this.widgetID = widgetID;
		this.state = state;
		this.event = event;
		this.target = target;
		this.description = description;
	}
	logObject.prototype = {
		valid : false, 		// FIXME: what is this?
		node : null,
		order : null,
		timestamp : null,
		widgetID : null,
		state : null,
		event : null,
		target : null,
		description : null,
		display : true,
		displayDetails: logObjectDisplayDetails,
		
		getTreeNode : function(windoc) {
			return accesxt_generateTreeboxResults(windoc,this,['order','timestamp','widgetID','state','event','target','description']);
		},
	}
AccessibilityExtension.LIB.extend(AccessibilityExtension.LIB,
{
	/**
	 * Function for loading the event log list headers.
	 * @return array of column headers for the widget list
	 * @author Sandy Foltz
	*/
	loadLogColumnHeaders: function() {
		var cols = new Array();
		cols['list-header-order'] = 'order';
		cols['list-header-timestamp'] = 'timestamp';	
		cols['list-header-widgetID'] = 'widgetID';
		cols['list-header-state'] = 'state';
		cols['list-header-event'] = 'event';
		cols['list-header-target'] = 'target';	
		cols['list-header-description'] = 'description';
	   return cols;	
	},
	
	initTreeboxDHTMLLog: function() {
		var logColumnHeaders = loadLogColumnHeaders();
		tbObj = newTreeboxObject('treebox',logColumnHeaders,'list-header-order');
	},
	
	updatePointers: function() {
		try { // dialog way first
			AE.thewindow = window.opener.parent._content;
		} catch(e) {
			AE.thewindow = window.parent._content;
		}
		AE.mydoc = AE.thewindow.document;
		AE.treebox = document.getElementById("treebox");
		AE.dataarray = new Array();
	
		initTreeboxDHTMLLog();
	},
	
	InitAccessibilityExt: function() {
		InitAccessibilityExt_common();
	
		accessext_setUserEventHandlers(false);
	
		// Capture DOM Mutate events 
		recursiveAddEventListener(AE.mydoc, "DOMAttrModified", accessext_eventAttrModified, true);	
	},
	
	// Copied from listofdhtml.js
	// FIXME: Mozilla has issues with its DOM Mutation Events.  Whatever gets implemented here will likely need to change.
	// FIXME: DOM Mutation events seem to be listed as cancelable in Mozilla but they are not in the DOM spec.  Someone could potentially 
	// ... cancel a state attribute change before it actually changed in the DOM and we'd be incorrect?
	accessext_eventAttrModified: function(event) {
		// relatedNode is not set when attribute is first removed.  The DOM spec indicates that the attribute should be 
		// ... removed from the tree before the DOMAttrModified event is fired, but Mozilla fires it before it is removed.  
		// ... The information we have available is the target element node, the attribute name, and the attribute's old and new values.
		// ... However, the important namespace information is missing from the attribute name.  Thus, if ever two "checked" attributes 
		// ... appear and the non-aaa one is removed, then we'll screw up (unlikely).
		var descriptionStr = "";
		var stateObj = null;
		
		// true if relatedNode is null or a state attribute
		if( event.relatedNode==null ? true : isStateAttribute(event.relatedNode) ) {
			// Find which widget node (in our dataarray) this attribute corresponds to 
			for(var i=0; i<AE.dataarray.length; i++) {
				if(AE.dataarray[i].id == event.target.id) {
					// If it is ADDITION, add this state to the array
					if(event.attrChange == event.ADDITION) {
						stateObj = AE.dataarray[i].states[AE.dataarray[i].states.length] = newaaaStateObject(event.relatedNode);
						descriptionStr = event.target.id + ' added state ' + stateObj.state + '=' + stateObj.value;					
					} else {
						// Find the appropriate existing state to modify
						for(var j=0; j<AE.dataarray[i].states.length; j++) {
						   	var eveAttName = event.attrName;
						   	if (event.attrName.indexOf('aria-') == 0) {
							 eveAttName = event.attrName.substring(5);
							}
							if(AE.dataarray[i].states[j].state == eveAttName) {
								stateObj = AE.dataarray[i].states[j];
								// If this is REMOVAL operation, remove this state from the array
								if(event.attrChange == event.REMOVAL) {
									actionStr = "removed";
									descriptionStr = event.target.id + ' removed state ' + stateObj.state + '=' + stateObj.value;						
									AE.dataarray[i].states.splice(j,1);	// remove the jth element
								}
								// If it is MODIFICATION, change the value
								if(event.attrChange == event.MODIFICATION) {
									actionStr = "modified";							
									AE.dataarray[i].states[j].lastValue = event.prevValue;
									AE.dataarray[i].states[j].value = event.newValue;
									descriptionStr = event.target.id + ' modified state ' + stateObj.state + ' from ' + event.prevValue + ' to ' + event.newValue;						
								}
							}
						}
					}
					break;
				}
			}
			// Create a new log message for this event
			logarray[logarray.length] = new logObject(
				event.target,
				logarray.length+1,
				event.timeStamp,
				event.target.id,
				stateObj.state,
				event.type,
				event.target.id,
				descriptionStr
			);
			RefreshListView();
		}
	},
	
	// Sets appropriate event logging handlers relative to what's checked in the dialog
	accessext_setUserEventHandlers: function() {
		for(var i=0; i<AE.dataarray.length; i++) {
			setNodeUserEventListeners(AE.dataarray[i].node);
		}
	},
	
	// Adjusts event handlers for each widget
	accessext_toggleUserEventHandlers: function(event) {
		// searches for event string in one list; adds if not found and removes from the other or vice-versa
		foundInAdd = false;
		for(var j=0; j<userEventsToAdd.length; j++) {
			if( userEventsToAdd[j] == event ) {
				userEventsToAdd.splice(j,1);  // delete entry
				foundInAdd = true;
				break;
			}
		}
		// Add string if never found
		if( !foundInAdd ) {
			userEventsToAdd.push(event);
			
			for(var j=0; j<userEventsToRemove.length; j++) {
				if( userEventsToRemove[j] == event ) {
					userEventsToRemove.splice(j,1);  // delete entry
					break;
				}
			}
		} else {
			userEventsToRemove.push(event);
		}
		
		accessext_setUserEventHandlers();
	},
	
	// Add event handlers to each widget
	setNodeUserEventListeners: function(node, toggle) {
		// Add events to all widget nodes	
		for(var j=0; j<userEventsToAdd.length; j++) {
			node.addEventListener(userEventsToAdd[j],accessext_eventUserInput,true);
		}
		
		// Remove events from all widget nodes
		for(var j=0; j<userEventsToRemove.length; j++) {
			node.removeEventListener(userEventsToRemove[j],accessext_eventUserInput,true);
		}
	},
	
	// Event listener for selected widget user input events
	accessext_eventUserInput: function(event) {
		// Create a new log message for this event
		var logindex = 0;
		if( logarray != [] ) {
			logindex = logarray.length;
		}
		
		var description = "";
		if(event.type == "keypress" || event.type == "keydown" || event.type == "keyup") {
			description = event.currentTarget.id + ' recieved ' + event.type + ' (keyCode=' + event.keyCode + ', charCode=' + event.charCode + ') for ' + event.target.id;
		} else {
			description = event.currentTarget.id + ' recieved ' + event.type + ' for ' + event.target.id;		
		}
		
		logarray[logindex] = new logObject(
			event.currentTarget,
			logindex+1,
			event.timeStamp,
			event.currentTarget.id,
			'',
			event.type,
			event.target.id,
			description
		);
		RefreshListView();
	},

	// Copied from RefreshList, should refresh the view without reloading dataarray from the DOM tree
	RefreshListView: function() {
		var tempCurrentIndex=AE.treebox.currentIndex;
	
		tbObj.setDataArray(logarray);
		UpdateTreeView();
		AE.treebox.view.selection.select(tempCurrentIndex);
	},
	
	// Scans the DOM to create a list of relevant objects and updates our tree view
	RefreshList: function() {
		updatePointers();
		
		var documents = accessext_getDocuments(AE.thewindow,new Array());
		
		for (var i=0; i < documents.length; i++) {
			ParseDocument(documents[i]);
		}
	
		tbObj.setDataArray(logarray);
		UpdateTreeView();
	},
	
	UpdateTreeView: function() {
		var logColumnHeaders = loadLogColumnHeaders();	
		UpdateTreeView_common(AE.treebox,'order',AE.sortColumn,logColumnHeaders,logarray);
	},
	
	accessext_clearLogList: function() {
		logarray = new Array();
		RefreshListView();
	},
	
	accessext_exportLogList: function() {
		var outputString;
		// Write log to page with some styling
		outputString = 
			'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n' + 
			'<html>\n' +
			'<head lang="en">\n' +
			'	<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />\n' +
			'	<title>ARIA User and DOM Event Log</title>\n' +
			'	<style type="text/css">\n' +
			'	table {\n' +
			'	width: 100%;\n' +
			'	border-width: 1px 1px 1px 1px;\n' +
			'	border-spacing: 3px;\n' +
			'	border-style: solid solid solid solid;\n' +
			'	border-color: black black black black;\n' +
			'	border-collapse: collapse;\n' +
			'	background-color: white;\n' +
			'}\n' +
			'\n' +
			'table caption {\n' +
			'	font-size: large;\n' +
			'}\n' +
			'\n' +
			'th {\n' +
			'	border-width: 1px 1px 1px 1px;\n' +
			'	padding: 2px 2px 2px 2px;\n' +
			'	border-style: inset inset inset inset;\n' +
			'	border-color: black black black black;\n' +
			'	background-color: lightgrey;\n' +
			'	-moz-border-radius: 0px 0px 0px 0px;\n' +
			'}\n' +
			'\n' +
			'td {\n' +
			'	border-width: 1px 1px 1px 1px;\n' +
			'	padding: .2em .2em .2em .2em;\n' +
			'	border-style: inset inset inset inset;\n' +
			'	border-color: black black black black;\n' +
			'	background-color: white;\n' +
			'	-moz-border-radius: 0px 0px 0px 0px;\n' +
			'}\n' +
			'	</style>\n' +
			'</head>\n' +
			'<body>\n' + 
			'<h1>ARIA User and DOM Event Log</h1>\n';
		
		outputString +=
			'<table title="ARIA Events Log" summary="Chronologically ordered list of logged ARIA events on page widgets">\n' +
			'	<caption>ARIA Events Log</caption>\n' +
			'	<thead>\n' +
			'		<tr>\n' +
			'			<th id="order">Order</th>\n' +
			'			<th id="time">Time</th>\n' +
			'			<th id="widget">Widget</th>\n' +
			'			<th id="state">State</th>\n' +
			'			<th id="event">Event</th>\n' +
			'			<th id="target">Target</th>\n' +
			'			<th id="description">Description</th>\n' +
			'		</tr>\n' +
			'	</thead>\n' +
			'	<tbody>\n';
	
														
		for(var i=0; i<logarray.length; i++) {
			outputString += '		<tr>\n';
			outputString += '			<td class="entry">' + logarray[i].order + '</td>'; 
			outputString += '			<td class="entry">' + logarray[i].timestamp + '</td>'; 
			outputString += '			<td class="entry">' + logarray[i].widgetID + '</td>'; 
			outputString += '			<td class="entry">' + logarray[i].state + '</td>'; 
			outputString += '			<td class="entry">' + logarray[i].event + '</td>'; 
			outputString += '			<td class="entry">' + logarray[i].target + '</td>'; 
			outputString += '			<td class="entry">' + logarray[i].description + '</td>'; 												
			outputString += '		</tr>\n';
		}
		
		outputString += '	</tbody>\n</table>\n</body>\n</html>';
		
		try {
			var accessibleHTMLPage = window.open("Template.html", "AccessExt as broswer page", 'status=1,toolbar=1,scrollbars=1,menubar=1') 
		} catch (ex) {
			accessext_consoleDump('accessExtAsBrowserPage exception: ' + ex);
		}
	    
		function tryAgain() {
			var bodyElement = accessibleHTMLPage.document.getElementById("bodyID");
	        if (bodyElement) {
	 			bodyElement.innerHTML = outputString;
	        }
	        else {
	             setTimeout (tryAgain, 500);
	        }
		}
	    try {
	 		var bodyElement = accessibleHTMLPage.document.getElementById("bodyID");
			if (!(bodyElement))
	            throw "tantrum";
			else {
	 			bodyElement.innerHTML = outputString;
			}       
	    }
	    catch (ex) {
	        setTimeout (tryAgain, 500);
	        return;
	    }// need to wait until this loads	
		
	},
	
	// Removes any existing handlers
	accessext_dhtmlLogClose: function() {
		try {
			AE.mydoc.removeEventListener("DOMAttrModified",accessext_eventAttrModified,true);
		   	CleanUp();
			
			userEventsToRemove = userEventsToAdd;
			accessext_setUserEventHandlers();
			self.close();	
		} catch (ex) {
			accessext_consoleDump('refreshLog exception: ', ex);
		}
	},
	
	refreshLog: function() {
		try {
			accessext_dhtmlLogClose();
			
			var tempCurrentIndex=document.getElementById('treebox').currentIndex;
			accessext_setUserEventHandlers(false);
			recursiveAddEventListener(AE.mydoc, "DOMAttrModified", accessext_eventAttrModified, true);	
			accessext_clearLogList();
			if (tempCurrentIndex > document.getElementById('treebox').length) {
				tempCurrentIndex = 0;
			}
			//alert('There may be a timing issue this alert helped at one point.')
			document.getElementById('treebox').view.selection.select(tempCurrentIndex);
			document.getElementById('treebox').focus();
		} catch (ex) {
			accessext_consoleDump('refreshLog exception: ', ex);
		}
	},
	
	// When a row in the treebox_main is selected, this is called
	accessext_handle_treebox: function() {
		// If we've selected something sensical
		if(AE.treebox.currentIndex < 0 || AE.treebox.currentIndex >= logarray.length)
			return;
	
		accessext_scrollAndHighlightNode(logarray[currentIndex].node);
	},
	
	accessext_handle_listbox: function() {
		UpdateTreeView();
	},
	
	ParseDocument: function(node) {
		var xpathResult = parseWidgetsFromDOM(node);	
		var r = xpathResult.iterateNext();
	
		while (r) {
			AE.dataarray[AE.dataarray.length] = newWidgetObject(AE.dataarray.length + 1, r);
			r = xpathResult.iterateNext();
		}
	}
});
	if (AccessibilityExtension.LIB.InitAccessibilityExt) AccessibilityExtension.LIB.InitAccessibilityExt();	
}});
